pub mod paser;
pub mod progress;
use console::style;
use indicatif::{ProgressBar, ProgressStyle};
use paser::httpget;
use regex::Regex;
use reqwest::Url;
use std::{
    fs::{self, create_dir_all, remove_dir_all, File},
    io::{BufRead, Write},
    path::{Path, PathBuf},
    process::Stdio,
    sync::Arc,
    time::Duration,
};

use tokio::{sync::Mutex, task::JoinSet};

/// M3U8文件
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct M3 {
    /// 本集名称[name.mp4]
    pub name: String,
    /// m3u8文件保存位置[ffmpeh -i m3path]
    pub m3path: PathBuf,
    /// m3u8文件下载地址
    pub m3url: String,
    /// 可空
    pub key: Option<Vec<u8>>,
    ///iv 可空
    pub iv: Option<String>,
    /// ts列表
    pub tslist: Vec<Url>,
    /// 保存目录位置
    pub save_path: PathBuf,
    pub mpv_path: PathBuf,
    /// 失败ts文件
    pub tserrlist: Vec<String>,
    /// 下载错误是否重新下载
    pub is_retry: bool,
}

// 最多并发数 同时也是channel背压的buffer大小
const BUFF_SIZE: usize = 4;
// 延迟下载
const YANCI: u64 = 2;
// 修改的m3u文件
const M3U8FILE: &str = "index.m3u8";
// 备份的原m3u文件
const M3U8FILETMP: &str = "tmp.m3u8";
// 取 KEY IV 的正则表达式
const KEYIV: &str = r#"#EXT-X-KEY:METHOD=AES-128,URI="(.*?)",IV=(.+)"#;
// 取 ts 文件地址
const TSFILE: &str = r#"(.*?\.ts)|/(.*?\.ts)"#;
// 取 m3u8 文件地址
const M3URE: &str = r#"(.*?index.m3u8)"#;
impl M3 {
    /// 传入name:本集名称 url:m3u8文件下载地址 path:保存目录位置
    pub async fn new(name: &str, url: &str, path: String) -> Self {
        // 字符串转 Url
        let mut u = Url::parse(url).unwrap();
        let mpv_path = Path::new(&path).to_path_buf();
        // 保存文件夹路径
        let save_path = Path::new(&path).join(name);
        // M3U文件路径 【用于FFmpeg合并视频】
        let m3path = save_path.join(M3U8FILE);
        // m3u8 原备份
        let m3pathtmp = save_path.join(M3U8FILETMP);
        // 创建目录【可多级目录】
        create_dir_all(save_path.clone()).expect("【ERROR】创建目录失败！");
        // 获取m3u8文件
        let mut res = String::from_utf8(match httpget(u.clone()).await {
            Ok(res) => res,
            Err(err) => {
                println!(
                    "[ERROR]：{:#?}",
                    "获取m3u8文件出错！\n 请检查网络是否可用！"
                );
                panic!("{:#?}", err.source())
            }
        })
        .unwrap();
        // 识别ts文件 正则
        let re: Regex = Regex::new(TSFILE).unwrap();
        // 是否包含ts文件
        if !re.is_match(res.as_str()) {
            // 嵌套m3u文件
            // 识别嵌套文件
            let re: Regex = Regex::new(M3URE).unwrap();
            let cap = re.captures(res.as_str()).unwrap();
            let m3 = cap.get(1).unwrap().as_str();
            u = u.clone().join(m3).unwrap();
            // 重新获取真实的 m3u文件
            let res_new = httpget(u.clone().join(m3).unwrap()).await.unwrap();
            // 替换上面的m3u文件内容变量
            res = String::from_utf8(res_new).unwrap();
        }
        // println!(" ---------->{:#?}", a);
        // 备份 m3u 文件
        fs::write(m3pathtmp, res.as_bytes()).unwrap();
        // 创建一个文件，如果文件不存在则创建，如果文件已存在则覆盖。
        let mut fileout = File::create(m3path.clone()).unwrap();
        // 临时 tsUrl列表
        let mut tslist: Vec<Url> = vec![];
        // 取ts文件列表
        let re: Regex = Regex::new(TSFILE).unwrap();
        let a = re.captures_iter(&res);
        let mut res2 = res.clone();
        for cap in a {
            let tmp = &cap[0];
            if tmp.starts_with("http") {
                // 如果是全网址 直接加入数组
                tslist.push(tmp.parse().unwrap());
                // 取ts文件名称[也可用 path解析]
                let tsname = tmp.split("/").last().unwrap();
                let ts: Regex = Regex::new(tmp).unwrap();
                res2 = ts.replace(&res2, tsname).to_string();
            } else {
                let tmp2 = u.join(tmp).unwrap();
                let tsname = tmp2.path().split("/").last().unwrap();
                // println!("{:#?}", tmp2);
                res2 = res2.replace(tmp, tsname);
                // 下面是正则替换
                // let ts: Regex = Regex::new(tmp).unwrap();
                // res2 = ts.replace(&res2, tsname).to_string();
                tslist.push(tmp2);
            }
        }
        fileout.write_all(res2.as_bytes()).unwrap();
        fileout.sync_all().unwrap();
        // 返回
        Self {
            name: name.to_string(),
            m3url: u.to_string(),
            key: None,
            iv: None,
            tslist,
            save_path,
            m3path,
            tserrlist: vec![],
            is_retry: false,
            mpv_path,
        }
    }
    /// 返回是否bool 是否需要解密（如需解密 保存key和iv）
    async fn ifor(&mut self, url: Url, m3: &str) -> bool {
        let re: Regex = Regex::new(KEYIV).unwrap();
        // 判断是否有密钥
        if let Some(caps) = re.captures(m3) {
            // 去除 KEY这行
            let m3outpath = Path::new(&self.save_path.clone()).join(M3U8FILE);
            let buf = fs::read_to_string(m3outpath.clone()).expect("无法读取文件");
            let buf_out = re.replace(&buf, "").to_string();
            fs::write(m3outpath, buf_out).expect("无法写入文件");
            //------------------------
            let key = caps.get(1).unwrap().as_str();
            let iv = caps.get(2).unwrap().as_str();
            // 判断Url 开头 是不是 http
            if key.starts_with("http") {
                let k = paser::httpget(key.parse().unwrap()).await.unwrap();
                self.key = Some(k);
            } else {
                // key 网址不是http开头
                let k = paser::httpget(url.join(key).unwrap()).await.unwrap();
                self.key = Some(k);
            }
            self.iv = Some(iv.to_string());
            println!("Key:\t{:?}", String::from_utf8(self.key.clone().unwrap()));
            return true;
        }
        false
    }

    /// 解析并下载ts
    pub async fn paser(&mut self) -> &mut M3 {
        // 创建一个最大并发为`BUFF_SIZE`的信号量
        let mut joinset = JoinSet::new();
        let semaphore = Arc::new(tokio::sync::Semaphore::new(BUFF_SIZE));
        let pb = ProgressBar::new(self.tslist.clone().len().try_into().unwrap());
        let sty = ProgressStyle::with_template(
            "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
        )
        .unwrap()
        .progress_chars("##-");
        pb.set_style(sty.clone());
        let pbm = Arc::new(Mutex::new(pb));
        let url = &self.m3url.clone();
        let m3 = fs::read_to_string(self.m3path.clone()).unwrap();
        println!("{} {}", style("[开始下载]").bold().dim(), self.name);

        if self.ifor(url.parse().unwrap(), &m3).await {
            // 被加密需要解密 ___________________________________________________________________________________
            for ts in self.tslist.clone() {
                // println!("{}", ts);
                let key2 = self.key.clone().unwrap();
                let iv2 = self.iv.clone().unwrap();
                let save_path2 = self.save_path.clone();
                let semap = semaphore.clone();
                let _pbm = pbm.clone();
                joinset.spawn(async move {
                    if let Err(err) =
                        paser::passwd_paser(&semap, ts.clone(), &key2, iv2, save_path2).await
                    {
                        println!("[{}]\t{}->{}", progress::ERROR, err, ts);
                    };
                    _pbm.lock().await.inc(1);
                    std::thread::sleep(Duration::from_secs(YANCI));
                });
            }
        } else {
            // 没有被加密 （直接下载->然后合并）___________________________________________________________________________________
            for ts in self.tslist.clone() {
                // println!("{}", ts);
                let save_path = self.save_path.clone();
                let semap = semaphore.clone();
                let _pbm = pbm.clone();
                joinset.spawn(async move {
                    if let Err(err) = paser::nopasswd_paser(&semap, ts.clone(), save_path).await {
                        println!("[{}]msg:{}->{}", progress::ERROR, err, ts);
                    };
                    _pbm.lock().await.inc(1);
                    std::thread::sleep(Duration::from_secs(YANCI));
                });
            }
        }
        // 等待任意任务完成，不是按照任务开始顺序，当所有任务完成，退出循环
        while let Some(Ok(())) = joinset.join_next().await {
            // println!("task finished");
        }
        pbm.lock().await.finish();

        return self;
    }
    /// 合并（是否删除ts文件）
    pub async fn merge(&mut self, isdel: bool) {
        if isdel {
            let name = self.name.clone();
            let outpath = self.save_path.clone();
            let mpvpath = self.mpv_path.clone();
            tokio::spawn(async move {
                ts2mp4(name, outpath, mpvpath).await;
            });
        }
    }
}

async fn ts2mp4(name: String, outpath: PathBuf, mpvpath: PathBuf) {
    let indexpath = outpath.join("index.m3u8");
    let mp4path = mpvpath.join(name + ".mp4");
    println!("{:#?}-{:#?}", indexpath, mp4path);
    let mut command = std::process::Command::new("ffmpeg")
        .args([
            "-i",
            &indexpath.to_string_lossy().to_string(),
            "-c",
            "copy",
            &mp4path.to_string_lossy().to_string(),
        ])
        .stdout(Stdio::piped())
        .spawn()
        .expect("执行命令失败!!!X");
    let stdout = command.stdout.as_mut().expect("获取输出流失败!!!X");
    let reader = std::io::BufReader::new(stdout);
    for line in reader.lines() {
        println!("{}", line.unwrap());
    }
    remove_dir_all(outpath).expect("删除ouput目录失败");
}

// 合并
// ffmpeg -i index.m3u8 -c copy out.mp4
